package jamirr;

import jamirr.loader.ServiceLoaderImpl;

import java.util.*;

final class AnnotationReferenceLoader {

    public static final AnnotationReferenceLoader INSTANCE = new AnnotationReferenceLoader();

    private final ClassLoader classLoader;
    private Map<String, AnnotationReference> instanceMap;

    public AnnotationReferenceLoader() {
        this.classLoader = AnnotationReferenceLoader.class.getClassLoader();
    }

    public AnnotationReferenceLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public AnnotationReference getInstance(String annotationName) {
        return findInstance(annotationName).orElseThrow(() -> new RuntimeException("No AnnotationReference available for [" + annotationName + "]."));
    }

    @SuppressWarnings({"java:S1181"})
    public Optional<AnnotationReference> findInstance(String annotationName) {
        Objects.requireNonNull(annotationName, "annotationName cannot be null");
        final AnnotationReference reference = getInstanceMap().get(annotationName);
        try {
            if (reference != null) {
                return Optional.of(reference);
            } else {
                return Optional.empty();
            }
        } catch (Throwable e) {
            throw new RuntimeException("Error loading AnnotationReference for [" + annotationName + "]: " + e.getMessage(), e);
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, AnnotationReference> getInstanceMap() {
        Map<String, AnnotationReference> resolvedMap = this.instanceMap;
        if (resolvedMap == null) {
            synchronized (this) { // double check
                resolvedMap = this.instanceMap;
                if (resolvedMap == null) {
                    resolvedMap = new HashMap<>(30);
                    final ServiceLoaderImpl<AnnotationReference> services = loadReferences();
                    List<AnnotationReference> references = new ArrayList<>(300);
                    services.collectAll(
                            references,
                            t -> true
                    );
                    for (AnnotationReference reference : references) {
                        resolvedMap.put(reference.getAnnotationName(), reference);
                    }
                    this.instanceMap = resolvedMap;
                }
            }
        }
        return resolvedMap;
    }

    @SuppressWarnings({"java:S3740", "rawtypes"})
    private ServiceLoaderImpl loadReferences() {
        return ServiceLoaderImpl.load(AnnotationReference.class, classLoader);
    }
}
